inline-c
is a small crate that allows a user to write C (including
C++) code inside Rust. Both environments are strictly sandboxed: it is
non-obvious for a value to cross the boundary. The C code is
transformed into a string which is written in a temporary file. This
file is then compiled into an object file, that is finally
executed. It is possible to run assertions about the execution of the
C program.
The primary goal of inline-c
is to ease the testing of a C API of a
Rust program (generated with
cbindgen
for example). Note
that it's not tied to a Rust program exclusively, it's just its
initial reason to live.
Install
Add the following lines to your Cargo.toml
file:
[]
= "0.1"
Documentation
The assert_c
and assert_cxx
macros live in the inline-c-macro
crate, but are re-exported in this crate for the sake of simplicity.
Being able to write C code directly in Rust offers nice opportunities,
like having C examples inside the Rust documentation that are
executable and thus tested (with cargo test --doc
). Let's dig into
some examples.
Basic usage
The following example is super basic: C prints Hello, World!
on the
standard output, and Rust asserts that.
use assert_c;
Or with a C++ program:
use assert_cxx;
The assert_c
and assert_cxx
macros return a Result<Assert, Box<dyn Error>>
. See Assert
to learn more about the possible
assertions.
The following example tests the returned value:
use assert_c;
Environment variables
It is possible to define environment variables for the execution of
the given C program. The syntax is using the special #inline_c_rs
C
directive with the following syntax:
#inline_c_rs <variable_name>: "<variable_value>"
Please note the double quotes around the variable value.
use assert_c;
Meta environment variables
Using the #inline_c_rs
C directive can be repetitive if one needs to
define the same environment variable again and again. That's why meta
environment variables exist. They have the following syntax:
INLINE_C_RS_<variable_name>=<variable_value>
It is usually best to define them in a build.rs
script
for example. Let's see it in action with a tiny example:
use assert_c;
use ;
CFLAGS
, CPPFLAGS
, CXXFLAGS
and LDFLAGS
Some classical Makefile
variables like CFLAGS
, CPPFLAGS
,
CXXFLAGS
and LDFLAGS
are understood by inline-c
and consequently
have a special treatment. Their values are added to the appropriate
compilers when the C code is compiled and linked into an object file.
Pro tip: Let's say we have a Rust crate named foo
, and it exports a
C API. It is possible to define CFLAGS
and LDFLAGS
as follow to
correctly compile and link all the C codes to the Rust libfoo
shared
object by writing this in a build.rs
script (it is assumed that
libfoo
lands in the target/<profile>/
directory, and that foo.h
lands in the root directory):
use ;
Et voilà ! Now run cargo build --release
(to generate the
shared objects) and then cargo test --release
to see it in
action.
Using inline-c
inside Rust documentation
Since it is now possible to write C code inside Rust, it is consequently possible to write C examples, that are:
- Part of the Rust documentation with
cargo doc
, and - Tested with all the other Rust examples with
cargo test --doc
.
Yes. Testing C code with cargo test --doc
. How fun is that? No
trick needed. One can write:
/// Blah blah blah.
///
/// # Example
///
/// ```rust
/// # use inline_c::assert_c;
/// #
/// # fn main() {
/// # (assert_c! {
/// #include <stdio.h>
///
/// int main() {
/// printf("Hello, World!");
///
/// return 0;
/// }
/// # })
/// # .success()
/// # .stdout("Hello, World!");
/// # }
/// ```
pub extern "C"
which will compile down into something like this:
int
Notice that this example above is actually Rust code, with C code
inside. Only the C code is printed, due to the #
hack of rustdoc
,
but this example is a valid Rust example, and is fully tested!
There is one minor caveat though: the highlighting. The Rust set of
rules are applied, rather than the C ruleset. See this issue on
rustdoc
to follow the
fix.
C macros
C macros with the #define
directive is supported only with Rust
nightly. One can write:
use assert_c;
Note that multi-lines macros don't work! That's because the \
symbol
is consumed by the Rust lexer. The best workaround is to define the
macro in another .h
file, and to include it with the #include
directive.
Who is using it?
- Wasmer, the leading WebAssembly runtime,
- Cargo C, to build and install
C-compatible libraries; it configures
inline-c
for you when usingcargo ctest
! - Biscuit, an authorization token microservices architectures.
License
BSD-3-Clause
, see LICENSE.md
.